package FlexTrade;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.swing.event.EventListenerList;

import net.jini.config.Configuration;
import net.jini.config.ConfigurationProvider;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.TransactionConstants;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.transaction.server.TransactionParticipant;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.security.ProxyPreparer;
import common_classes.AuctionPrimitiveData;
import common_classes.AuctionStates;
import common_classes.BidPriceData;
import common_classes.CustomerData;
import common_classes.IAuction;
import common_classes.IRemoteAccounts;
import common_classes.WSEvent;

public abstract class AbstractAuction implements IAuction, ProxyAccessor {
	/**
	 * 
	 */
	protected static final long serialVersionUID = 1L;
	protected static final long WAITFOR = 60000L;


	//Information about last Bid Price from customer
	protected BidPriceData lastBidPriceData;
	//Information about last customer join bid
	protected CustomerData lastCustomerData;
	//Information about this auction
	protected AuctionPrimitiveData auctionPrimitiveData; 
	//Current state of this auction
	protected AuctionStates auctionStates;
	protected EventListenerList listenerList = new EventListenerList();
	//remote proxy of this object
	protected Remote proxy;
	protected long seqNum = 0L;
	//remote bank service
	IRemoteAccounts rma = null;
	long crashCount = 0;

	public String uniqueAuctionID; 	//auction unique ID
	
	public Hashtable<String, ArrayList<WSEvent>> eventPool;


	public AbstractAuction(String _uniqueAuctionID) {
		this.uniqueAuctionID = _uniqueAuctionID;
		auctionStates = new AuctionStates();
		eventPool = new Hashtable<String, ArrayList<WSEvent>>();
	}
	
	public AbstractAuction(){
		auctionStates = new AuctionStates();
		eventPool = new Hashtable<String, ArrayList<WSEvent>>();
	}

	public AbstractAuction(AuctionPrimitiveData _auctionPrimitiveData) {
		// TODO Auto-generated constructor stub
		auctionStates = new AuctionStates();
		this.auctionPrimitiveData = _auctionPrimitiveData;
		this.uniqueAuctionID = _auctionPrimitiveData.uniqueAuctionID;
		eventPool = new Hashtable<String, ArrayList<WSEvent>>();
	}
	
	
	@Override
	public boolean receiveBidPriceFromCustomer(BidPriceData _bidPriceData) 
														throws RemoteException {
		return false;
	}
	
	@Override
	public boolean receiveNewAcceptablePriceFromProvider(
				float newAcceptablePrice) throws RemoteException {
		// TODO Auto-generated method stub
		return false;		
	}

	@Override
	public boolean receivePriceAcceptanceFromCustomer(BidPriceData _bidPriceData)
			throws RemoteException {
		// TODO Auto-generated method stub
		return false;		
	}

	@Override
	public EventRegistration addRemoteListener(RemoteEventListener listener) 
												throws RemoteException {
		// TODO Auto-generated method stub
		long eventID;
		listenerList.add(RemoteEventListener.class, listener);
		
		//notify provider and other clients
		switch (this.auctionPrimitiveData.auctionType) {
		case IAuction.VICKREY_AUCTION_TYPE:
			eventID = IAuction.V_CUSTOMER_JOIN;
			break;
		case IAuction.ENGLISH_AUCTION_TYPE:
			eventID = IAuction.E_CUSTOMER_JOIN;
			break;
		case IAuction.DUTCH_AUCTION_TYPE:
			eventID = IAuction.D_CUSTOMER_JOIN;
			break;
		default:
			eventID = 0;
		}
		fireNotify(eventID);
		putEventToPool(eventID);
		return new EventRegistration(0,
		proxy,
		null, /* Lease is null for simplicity only.
		It should be e.g. a LandlordLease
		*/
		0);
	}


	public Object getProxy() {
		return proxy;
	}
	
	
	@Override
	public void setProxy(Remote _proxy) throws RemoteException {
		// TODO Auto-generated method stub
		this.proxy = _proxy;
	}
	
	public void setAuctionPrimitiveData(AuctionPrimitiveData _auctionPrimitiveData)
			throws RemoteException {
		this.auctionPrimitiveData = _auctionPrimitiveData;
		this.uniqueAuctionID = _auctionPrimitiveData.uniqueAuctionID;
	}

	
	//find remote bank service from LUS
	protected IRemoteAccounts lookUpAccountService(){
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		Class [] classes = new Class[] {IRemoteAccounts.class};
		ServiceTemplate template = new ServiceTemplate(null, classes,null);
		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										AbstractAuction.WAITFOR); // timeout
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		if (item == null) {
			// couldn't find a service in time
			System.out.println("No remote account service found.");
			return null;
			//System.exit(1);
		}
		// Get the service
		IRemoteAccounts t_rma = (IRemoteAccounts) item.service;
		
		if (t_rma != null) {
			ProxyPreparer preparer = null;
			try {
				// get the configuration (by default a FileConfiguration)
				/*Configuration config =  ConfigurationProvider.getInstance(new String[] {Market.CONFIG_FILE});
				// and use this to construct an exporter
				preparer = (ProxyPreparer) config.getEntry(
							"ProxyPreparer", "preparer", ProxyPreparer.class);
				t_rma = (IRemoteAccounts) preparer.prepareProxy(t_rma);*/
				return t_rma;
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	//raise event to all listener about status change of this object
	protected void fireNotify(long eventID) {
		RemoteEvent remoteEvent = null;
		// Guaranteed to return a non-null array
		Object[] listeners = listenerList.getListenerList();
		// Process the listeners last to first, notifying
		// those that are interested in this event
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == RemoteEventListener.class) {
				RemoteEventListener listener = (RemoteEventListener) listeners[i+1];
				if (remoteEvent == null) {
					remoteEvent = new RemoteEvent(proxy, eventID, seqNum++, null);
				}
				try {
					listener.notify(remoteEvent);
				} catch(UnknownEventException e) {
					e.printStackTrace();
				} catch(RemoteException e) {
					e.printStackTrace();
					System.out.println("notification failed");
				}
			}
		}
	}
	
	protected TransactionManager findTxnMgr() {
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		Class [] classes = new Class[] {TransactionManager.class};
		ServiceTemplate template = new ServiceTemplate(null, classes,null);
		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										AbstractAuction.WAITFOR); // timeout
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		if (item == null) {
			// couldn't find a service in time
			System.out.println("No transaction manager service.");
			return null;
			//System.exit(1);
		}
		// Get the service
		TransactionManager mgr = (TransactionManager) item.service;
		if (mgr != null) {
			/*ProxyPreparer preparer = null;
			try {
				// get the configuration (by default a FileConfiguration)
				Configuration config =  ConfigurationProvider.getInstance(new String[] {Market.CONFIG_FILE});
				// and use this to construct an exporter
				preparer = (ProxyPreparer) config.getEntry(
							"ProxyPreparer", "preparer", ProxyPreparer.class);
				mgr = (TransactionManager) preparer.prepareProxy(mgr);
				return mgr;
			} catch(Exception e) {
				e.printStackTrace();
			}*/
			return mgr;
		}
		return null;
	}
	
	@Override
	public String getUniqueAuctionID() throws RemoteException {
		// TODO Auto-generated method stub
		return this.uniqueAuctionID;
	}

	//transfer payment from customer bank account to provider bank account
	@Override
	public boolean paymentTransfer(float amount, String customerID, String providerID)
				throws RemoteException {
		// TODO Auto-generated method stub
		long transactionID;
		TransactionManager mgr = findTxnMgr();
		TransactionManager.Created tcs = null;
		String[] configArgs = new String[] {Market.CONFIG_FILE};
		try {
			// get the configuration (by default a FileConfiguration)
			Configuration config =  ConfigurationProvider.getInstance(configArgs);
			// and use this to construct an exporter
			Exporter exporter = (Exporter) ((net.jini.config.Configuration) config).getEntry(
			"JeriExport", "exporter", Exporter.class);
			// export an object of this class
			proxy = exporter.export(this);
		} catch(Exception e) {
			e.printStackTrace();
			return false;
		}
		try {
			tcs = mgr.create(Lease.FOREVER);
			transactionID = tcs.id;
			mgr.join(transactionID, (TransactionParticipant)proxy, crashCount);
		} catch(Exception e) {
			e.printStackTrace();
			return false;
		}
		new LeaseRenewalManager().renewUntil(tcs.lease, Lease.FOREVER, null);
		rma = lookUpAccountService();
		if (rma != null) {
			try{	//if remote bank service found, transfer payment
				rma.creditDebit(amount, customerID, providerID, transactionID, mgr);
				return true;
			} catch(RemoteException e){
				e.printStackTrace();
			}
		}
		return false;
	}

	@Override
	public void abort(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		
		//notify customer and provider that paymet cannot be done
		putEventToPool(IAuction.CANNOT_TRANSFER_PAYMENT);
		//fireNotify(IAuction.CANNOT_TRANSFER_PAYMENT);
		this.auctionStates.currentStatus = IAuction.AUCTION_FINISHED;
		System.out.println("Transaction " + Long.toString(arg1) + " is aborted");
		
	}

	@Override
	public void commit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		
		//notify customer and provider that payment is transfered successfully
		putEventToPool(IAuction.PAYMENT_TRANSFERED_SUCCESSFULLY);
		//fireNotify(IAuction.PAYMENT_TRANSFERED_SUCCESSFULLY);
		this.auctionStates.currentStatus = IAuction.AUCTION_FINISHED;
		System.out.println("Auction transaction " + Long.toString(arg1) + " is committed");
		
	}


	@Override
	public int prepare(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		return TransactionConstants.PREPARED;
	}

	@Override
	public int prepareAndCommit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		int result = prepare(arg0, arg1);
		if(result == TransactionConstants.PREPARED) {
			commit(arg0, arg1);
			result = TransactionConstants.COMMITTED;
		}
		return result;
	}
	
	public BidPriceData getLastBidPriceData() throws RemoteException {
		return this.lastBidPriceData;
	}
	
	public AuctionStates getCurrentAuctionStates() throws RemoteException {
		return this.auctionStates;
	}
	
	public CustomerData getLastJoinCustomer() throws RemoteException {
		return this.lastCustomerData;
	}

	@Override
	public AuctionPrimitiveData getAuctionPrimitiveData()
			throws RemoteException {
		// TODO Auto-generated method stub
		return this.auctionPrimitiveData;
	}
	
	@Override
	public synchronized void putEventToPool(long _eventID) {
		WSEvent event = new WSEvent();
		ArrayList<WSEvent> temp;
		event.eventID = _eventID;
		event.auctionPrimitiveData = this.auctionPrimitiveData;
		event.auctionStates = this.auctionStates; //new AuctionStates(this.auctionStates);
		event.lastBidPriceData = this.lastBidPriceData;
		Enumeration<String> e = eventPool.keys();
		while (e.hasMoreElements()) {
			String key = e.nextElement();
			temp = (ArrayList<WSEvent>) eventPool.get(key);
			temp.add(event);
		}
	}
	
	@Override
	public ArrayList<WSEvent> pullWSEventsFromAuction(String _customerID) 
				throws RemoteException {
		// TODO Auto-generated method stub
		ArrayList<WSEvent> eventList = eventPool.remove(_customerID);
		if (eventList != null) {
			ArrayList<WSEvent> arrayList = new ArrayList<WSEvent>();
			eventPool.put(_customerID, arrayList);
			return eventList;
		}
		return null;
	}
	
	
	@Override
	public void customerJoinAuction(String _customerID) {
		ArrayList<WSEvent> arrayList = eventPool.get(_customerID);
		if (arrayList == null) {
			arrayList = new ArrayList<WSEvent>();
			eventPool.put(_customerID, arrayList);
		}
	}
	
	@Override
	public Hashtable<String, ArrayList<WSEvent>> getEventPool() throws RemoteException {
		return this.eventPool;
	}
	
	@Override
	public void setEventPool(Hashtable<String, ArrayList<WSEvent>> _eventPool) 
				throws RemoteException {
		this.eventPool = _eventPool;
	}
	
	public void setAuctionStates(AuctionStates _auctionStates) {
		this.auctionStates = _auctionStates;
	}
	
}
